package com.singfan.common.db;

import com.singfan.common.util.IPUtil;
import com.singfan.common.SingFanException;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

/**
 * 主键生成器（注意：1.Web Server的ip地址第4段不能相同，2.原始表的个数不能超过256个，3.表的升级版本不能超过16个版本，4.单个Server每秒钟每个表能产生8192个id 5.只能保证34年内不重复）
 * 创建规则：(timestamp 30) + (web server ip 8) + (table type 8) + (version 4) + (index 13)
 * Author: HanSongHe
 * Date: 15-7-14 下午6:16
 */
public class PrimaryKeyBuilder {
    private PrimaryKeyBuilder(){
        Calendar calendar = Calendar.getInstance();
        calendar.set(2015,Calendar.JANUARY,1,0,0,0); //这个时间在同一个项目中千万不要改变，否则很可能出现重复的PrimaryKey
        originalTime = calendar.getTime().getTime();

        String ipLastSection = IPUtil.getHostIpLastSection();
        ipPart = fillSeats(Integer.toBinaryString(Integer.valueOf(ipLastSection)), IP_SEAT);

        indexMax = getMaxValue(INDEX_SEAT);
        tableTypeMax = getMaxValue(TABLE_TYPE_SEAT);
        tableVersionMax = getMaxValue(TABLE_VERSION_SEAT);
    }

    private static final String SEAT = "0";
    private static final int TIMESTAMP_SEAT = 30;
    private static final int IP_SEAT = 8;
    private static final int TABLE_TYPE_SEAT = 8;
    private static final int TABLE_VERSION_SEAT = 4;
    private static final int INDEX_SEAT = 13;
    private static final int PRIMARY_KEY_LENGTH = 63;

    private static final int TABLE_TYPE_BEGIN_INDEX = TIMESTAMP_SEAT + IP_SEAT;
    private static final int TABLE_TYPE_END_INDEX = TIMESTAMP_SEAT + IP_SEAT + TABLE_TYPE_SEAT;
    private static final int TABLE_VERSION_BEGIN_INDEX = TIMESTAMP_SEAT + IP_SEAT + TABLE_TYPE_SEAT;
    private static final int TABLE_VERSION_END_INDEX = TIMESTAMP_SEAT + IP_SEAT + TABLE_TYPE_SEAT + TABLE_VERSION_SEAT;

    private static PrimaryKeyBuilder primaryKeyBuilder = new PrimaryKeyBuilder();

    private long originalTime;
    private String ipPart;

    private int indexMax;
    private int tableTypeMax;
    private int tableVersionMax;

    private long lastTimestamp = 0;

    private Map<Integer, Integer> indexMap = new HashMap<>();

    public static PrimaryKeyBuilder getInstance(){
        return primaryKeyBuilder;
    }

    /**
     * 根据表的版本号和类别生成主键
     * @param tableType 表的类别
     * @param tableVersion 表的版本号
     * @return 主键
     */
    public synchronized long build(int tableType, int tableVersion){
        if(tableType > tableTypeMax){
            throw new SingFanException(String.format("PrimaryKeyBuilder.build() error: tableType(%1$d) is already overtake tableTypeMax(%2$d) !", tableType, tableTypeMax));
        }
        else if(tableType < 0){
            throw new SingFanException(String.format("PrimaryKeyBuilder.build() error: tableType(%1$d) is error type !", tableType));
        }

        if(tableVersion > tableVersionMax){
            throw new SingFanException(String.format("PrimaryKeyBuilder.build() error: tableVersion(%1$d) is already overtake tableVersionMax(%2$d) !", tableVersion, tableVersionMax));
        }
        else if(tableVersion < 0){
            throw new SingFanException(String.format("PrimaryKeyBuilder.build() error: tableVersion(%1$d) is error type !", tableVersion));
        }

        long timestamp = (System.currentTimeMillis() - originalTime) / 1000;
        Integer index = indexMap.get(tableType);
        if(timestamp > lastTimestamp){
            index = 0;
            lastTimestamp = timestamp;
        }
        else{
            if (index==null){
                index = 0;
            }
            else if(index < indexMax){
                index++;
            }
            else{
                throw new SingFanException(String.format("PrimaryKeyBuilder.build() error: index(%1$d) is already overtake indexMax(%2$d) !", index, indexMax));
            }
        }
        indexMap.put(tableType, index);

        String timestampPart = getTimestempPart(timestamp);
        String tableTypePart = getTableTypePart(tableType);
        String tableVersionPart = getTableVsersionPart(tableVersion);
        String indexPart = getIndexPart(index);

        StringBuilder stringBuilder = new StringBuilder(PRIMARY_KEY_LENGTH);
        stringBuilder.append(timestampPart);
        stringBuilder.append(ipPart);
        stringBuilder.append(tableTypePart);
        stringBuilder.append(tableVersionPart);
        stringBuilder.append(indexPart);

        String primaryKey = stringBuilder.toString();

        return Long.valueOf(primaryKey, 2);
    }

    /**
     * 获取当前主键被创建的时间
     * @param primaryKey 主键
     * @return 被创建的时间
     */
    public long getCreateTime(long primaryKey) {
        String fullPrimaryKey = fillSeats(Long.toBinaryString(primaryKey), PRIMARY_KEY_LENGTH);
        String timestamp = fullPrimaryKey.substring(0, TIMESTAMP_SEAT);
        long timestampValue = Long.valueOf(timestamp, 2);
        return timestampValue * 1000 + originalTime;
    }

    /**
     * 获取表的类型值
     * @param primaryKey 主键
     * @return 表的类型值
     */
    public int getTableType(long primaryKey){
        String fullPrimaryKey = fillSeats(Long.toBinaryString(primaryKey), PRIMARY_KEY_LENGTH);
        String tableType = fullPrimaryKey.substring(TABLE_TYPE_BEGIN_INDEX, TABLE_TYPE_END_INDEX);
        return Integer.valueOf(tableType, 2);
    }

    /**
     * 获取当前主键所包含的表的版本号
     * @param primaryKey 主键
     * @return 表的版本号
     */
    public int getTableVersion(long primaryKey){
        String fullPrimaryKey = fillSeats(Long.toBinaryString(primaryKey), PRIMARY_KEY_LENGTH);
        String tableVersion = fullPrimaryKey.substring(TABLE_VERSION_BEGIN_INDEX, TABLE_VERSION_END_INDEX);
        return Integer.valueOf(tableVersion, 2);
    }

    private String getTimestempPart(long timeValue){
        String time = Long.toBinaryString(timeValue);
        return fillSeats(time, TIMESTAMP_SEAT);
    }

    private String getTableTypePart(int tableType){
        return fillSeats(Integer.toBinaryString(tableType), TABLE_TYPE_SEAT);
    }

    private String getTableVsersionPart(int tableVersion){
        return fillSeats(Integer.toBinaryString(tableVersion), TABLE_VERSION_SEAT);
    }

    private String getIndexPart(int index){
        return fillSeats(Integer.toBinaryString(index), INDEX_SEAT);
    }

    private String fillSeats(String value, int size){
        String result;

        int valueLength = value.length();
        if(valueLength >= size){
            result = value;
        }
        else{
            int lackCount = size - valueLength;
            StringBuilder stringBuilder = new StringBuilder(lackCount + 1);
            for(int i = 0; i < lackCount; i++){
                stringBuilder.append(SEAT);
            }
            stringBuilder.append(value);
            result = stringBuilder.toString();
        }

        return result;
    }

    private int getMaxValue(int seats){
        return (2 << (seats - 1)) - 1;
    }
}
